﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Synapse.Framework.AoP;
using Synapse.Framework.IoC.Bind;
using Synapse.Framework.Notifier;
using Binder = Synapse.Framework.IoC.Bind.Binder;

namespace Synapse.Framework.IoC
{
    public class Container : IContainer
    {
        private readonly INotifier notifier;
        private readonly IAspectDecorator aspectDecorator;
        private readonly List<Bind.Binder> bindings;
        private bool withoutAspectDecoratorFlag;
        private readonly Stack<IDisposable> disposables; 


        public Container() : this(new TraceNotifier(), new AspectDecorator(new InvocationHandlerFactory()))
        {
        }

        public Container(INotifier notifier, IAspectDecorator aspectDecorator)
        {
            this.notifier = notifier;
            this.aspectDecorator = aspectDecorator;
            bindings = new List<Bind.Binder>();
            disposables = new Stack<IDisposable>();
        }

        public I Resolve<I>()
        {
            return (I)Resolve(typeof (I));
        }

        public object Resolve(Type type)
        {
            if (bindings.All(i => i.Contract != type && type.IsClass))
                Bind(type).To(type);

            var implemetation = ProvideBinder(type).Implementations.Single(i => i.Default);

            return Instance(type, implemetation);
        }

        private Bind.Binder ProvideBinder(Type type)
        {
            var binder = bindings.SingleOrDefault(i => i.Contract == type);
            if (binder == null)
            {
                foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try
                    {
                        if (!assembly.GetCustomAttributes(typeof (AssemblyInjectAttribute), false).Any())
                            continue;
                        
                        var classMachts = assembly.GetTypes().Where(t => t.IsClass && t.GetInterfaces().Any(i => i == type)).ToArray();
                        
                        if (!classMachts.Any())
                            throw new Exception(string.Format("For type [{0}], there aren't match between Bind constraint and implemented interfaces.",type.FullName));
                        
                        if (classMachts.Count() > 1)
                            throw new Exception(
                                string.Format("For type [{0}], there aren too match between Bind constraint and implemented interfaces.",type.FullName));
                        
                        binder = GetOrCreateBinder(type);
                        binder.To(classMachts[0]);
                    }
                    catch (Exception ex)
                    {
                        notifier.Notify(ex);
                    }
                }
            }
            return binder;
        }

        private object Resolve(Type contract, Type implementation)
        {
            Bind(contract).To(implementation);
            return Instance(contract, bindings.Single(i => i.Contract == contract).Implementations.Single(i => i.Type == implementation));
        }

        private object Instance(Type contract, Implemetation implementation)
        {
            if (implementation.InstanceDecorator != null && !implementation.IsNewInstance && contract.IsInterface)
                return implementation.InstanceDecorator;

            if (implementation.Instance == null)
            {
                IEnumerable<Implemetation> selectMany = bindings.SelectMany(i => i.Implementations);
                var matchInstance = selectMany.FirstOrDefault(i => i.Instance != null && i.Type == implementation.Type && !i.IsNewInstance);
                if (matchInstance != null)
                    implementation.Instance = matchInstance.Instance;
            }

            if (implementation.Instance != null && !implementation.IsNewInstance && contract.IsInterface && implementation.InstanceDecorator == null && !withoutAspectDecoratorFlag)
                return implementation.InstanceDecorator = aspectDecorator.Create(this, implementation.Instance, contract);

            if (implementation.Instance != null && !implementation.IsNewInstance)
                return implementation.Instance;

            implementation.Instance = Activator.CreateInstance(implementation.Type, Instances(implementation.Type));

            var disposable = implementation.Instance as IDisposable;
            if (disposable !=null)
                disposables.Push(disposable);
            
            if (contract.IsClass || withoutAspectDecoratorFlag)
                return implementation.Instance;

            return implementation.InstanceDecorator = aspectDecorator.Create(this, implementation.Instance, contract);
        }

        private object[] Instances(Type typeToActivate)
        {
            var parametersInfo = DefaultConstructorInfo(typeToActivate).GetParameters();
            var instances = new object[parametersInfo.Length];
            foreach (var parameter in parametersInfo)
                instances[parameter.Position] = ResolveByParameter(parameter);
            return instances;
        }

        private  object ResolveByParameter(ParameterInfo parameter)
        {
            var parameterType = parameter.ParameterType;
            var forceParameterType = (ToAttribute) parameter.GetCustomAttributes(typeof (ToAttribute), false).SingleOrDefault();
            if (forceParameterType != null)
            {
                if (parameterType.IsInterface)
                {
                    if (forceParameterType.Type() != null)
                        return Resolve(parameterType, forceParameterType.Type());

                    if (forceParameterType.Key() != null)
                        return Resolve(parameterType, bindings.Single(i => i.Contract == parameterType).Implementations.Single(i => forceParameterType.Key().Equals(i.Key, StringComparison.InvariantCultureIgnoreCase)).Type);
                }

                if (parameterType.IsClass)
                {
                    if (forceParameterType.Type() != null)
                        return Resolve(forceParameterType.Type());

                    if (forceParameterType.Key() != null)
                        return Resolve(bindings.Single(i => i.Contract == parameterType).Implementations.Single(i => i.Key.Equals(forceParameterType.Key(), StringComparison.InvariantCultureIgnoreCase)).Type);
                }
            }
            return Resolve(parameterType);
        }

        private static ConstructorInfo DefaultConstructorInfo(Type typeToActivate)
        {
            var constructorsInfo = typeToActivate.GetConstructors();
            if (constructorsInfo.Length == 0)
                throw new Exception(string.Format("There are not public Constructors for type: {0}", typeToActivate.Name));

            if ((constructorsInfo.Length == 1))
                return constructorsInfo[0];

            var constructorInfos = constructorsInfo.Where(i => i.GetCustomAttributes(typeof (DefaultConstructorAttribute), false).Any()).ToArray();
            
            if (!constructorInfos.Any())
                throw new Exception(string.Format("There is not default Constructor for type: {0}", typeToActivate.Name));

            if (constructorInfos.Count() > 1)
                throw new Exception(string.Format("There are too default Constructors for type: {0}",typeToActivate.Name));

            return constructorInfos[0];
        }

        public IBinder Bind(Type type)
        {
            return GetOrCreateBinder(type);
        }

        public IBinder<I> Bind<I>()
        {
            return new Binder<I>(GetOrCreateBinder(typeof(I)));
        }

        private Bind.Binder GetOrCreateBinder(Type type)
        {
            var binder = bindings.SingleOrDefault(i => i.Contract == type);
            if (binder == null)
            {
                binder = new Bind.Binder(type);
                bindings.Add(binder);
            }
            return binder;
        }

        public IContainer WithoutAspectDecorator()
        {
            withoutAspectDecoratorFlag = true;
            return this;
        }

        public void Initialize()
        {
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    if (!assembly.GetCustomAttributes(typeof (AssemblyInjectAttribute), false).Any())
                        continue;

                    foreach (var type in assembly.GetTypes().Where(t => t.IsClass))
                        foreach (BindAttribute attribute in type.GetCustomAttributes(typeof (BindAttribute), false))
                        {
                            var interfaces = type.GetInterfaces();
                            if (attribute.Type() !=null && interfaces.All(i => i != attribute.Type()))
                                throw new Exception(string.Format("For type [{0}], there aren't match between Bind constraint and implemented interfaces.", type.FullName));

                            var interfaceType = attribute.Type();
                            if (interfaceType == null && interfaces.Count() == 1)
                                interfaceType = interfaces.First();
                            
                            Bind(interfaceType).To(type);
                        }
                }
                catch (Exception ex)
                {
                    notifier.Notify(ex);
                }
            }
        }

        private bool disposed;

        public void Dispose()
        {
            if (disposed)
                return;
            disposed = true;

            foreach (var disposable in disposables)
                disposable.Dispose();
        }
    }

    
}